﻿using System;
using System.Collections;
using System.Collections.Specialized;
using System.Windows;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Markup;

namespace WpfApplication1
{
    [ContentProperty("Content")]
    public sealed class DocumentFragment : FrameworkElement
    {
        private static readonly DependencyProperty ContentProperty = DependencyProperty.Register(
            "Content",
            typeof(FrameworkContentElement),
            typeof(DocumentFragment));

        public FrameworkContentElement Content
        {
            get { return (FrameworkContentElement)GetValue(ContentProperty); }
            set { SetValue(ContentProperty, value); }
        }
    }

    public static class FlowDocumentService
    {
        public static readonly DependencyProperty BlocksSourceProperty;
        public static readonly DependencyProperty BlockTemplateProperty;

        static FlowDocumentService()
        {
            BlocksSourceProperty = DependencyProperty.RegisterAttached("BlocksSource", typeof(IEnumerable),
                typeof(FlowDocumentService), new FrameworkPropertyMetadata(null, OnPropertyChanged));

            BlockTemplateProperty = DependencyProperty.RegisterAttached("BlockTemplate", typeof(DataTemplate),
                typeof(FlowDocumentService), new FrameworkPropertyMetadata(null, OnPropertyChanged));
        }

        [AttachedPropertyBrowsableForType(typeof(FlowDocument))]
        public static IEnumerable GetBlocksSource(FlowDocument document)
        {
            return (IEnumerable)document.GetValue(BlocksSourceProperty);
        }

        [AttachedPropertyBrowsableForType(typeof(FlowDocument))]
        public static DataTemplate GetBlockTemplate(FlowDocument document)
        {

            return (DataTemplate)document.GetValue(BlockTemplateProperty);
        }

        public static void SetBlocksSource(FlowDocument document, IEnumerable value)
        {
            document.SetValue(BlocksSourceProperty, value);
        }

        public static void SetBlockTemplate(FlowDocument document, DataTemplate value)
        {
            document.SetValue(BlockTemplateProperty, value);
        }

        private static void OnPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            var doc = (FlowDocument)(sender);
            
            if (e.Property == BlocksSourceProperty)
            {
                if (e.OldValue != null)
                {
                    TearDownBinding(doc);
                }

                if (e.NewValue != null)
                {
                    SetupBinding(doc, (IEnumerable)(e.NewValue));
                }
            }

            if (e.Property == BlockTemplateProperty)
            {
                var source = GetBlocksSource(doc);

                if (source != null)
                {
                    TearDownBinding(doc);
                    SetupBinding(doc, GetBlocksSource(doc));
                }
            }
        }

        private static void CreateBlocks(FlowDocument doc, IEnumerable source)
        {
            var template = GetBlockTemplate(doc);

            if (template != null)
            {
                foreach (var obj in source)
                {
                    var block = (Block)LoadDataTemplate(template);

                    block.DataContext = obj;
                    doc.Blocks.Add(block);
                }
            }
        }

        private static FrameworkContentElement LoadDataTemplate(DataTemplate template)
        {
            var content = template.LoadContent();
            var fragment = content as DocumentFragment;

            if (fragment != null)
            {
                return fragment.Content;
            }

            return null;
        }

        private static void SetupBinding(FlowDocument doc, IEnumerable source)
        {
            var view = CollectionViewSource.GetDefaultView(source);
            var listener = new CollectionEventListener(doc);

            doc.Blocks.Clear();
            CreateBlocks(doc, source);

            CollectionChangedEventManager.AddListener(view, listener);
        }

        private static void TearDownBinding(FlowDocument doc)
        {
            
        }

        private sealed class CollectionEventListener : IWeakEventListener
        {
            private readonly WeakReference _doc;

            public CollectionEventListener(FlowDocument doc)
            {
                _doc = new WeakReference(doc);
            }

            public Boolean ReceiveWeakEvent(Type managerType, Object sender, EventArgs e)
            {
                var args = (e as NotifyCollectionChangedEventArgs);
                var coll = (sender as IEnumerable);

                if ((_doc.IsAlive) && (args != null) && (coll != null))
                {
                    var doc = (_doc.Target as FlowDocument);

                    if (doc != null)
                    {
                        if (args.Action == NotifyCollectionChangedAction.Add)
                        {
                            CreateBlocks(doc, args.NewItems);
                        }
                        else
                        {
                            doc.Blocks.Clear();
                            CreateBlocks(doc, coll);
                        }

                        return true;
                    }
                }

                return false;
            }
        }

    }
}
